package com.haiqiu.file.ftp.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ftp.FtpException;
import com.haiqiu.file.oss.config.OssConfig;
import com.haiqiu.tools.common.utils.HttpServletUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

/**
 * @author HaiQiu
 * ftp文件操作工具类
 */
public class FtpUtils {

    private static final Logger log = LoggerFactory.getLogger(OssConfig.class);


    public final static String CARVE = "/";

    public final static String CAS = ".";

    public final static String HORIZONTAL = "-";

    /**
     * 执行完操作是否返回当前目录
     */
    private static boolean backToPwd;

    /**
     * ftp客户端连接池
     */
    private static GenericObjectPool<FTPClient> pool;


    public static void init(GenericObjectPool<FTPClient> pool) {
        FtpUtils.pool = pool;
    }

    public static FTPClient getFtp() {
        FTPClient ftp = borrowObject();
        if (ftp == null || !ftp.isConnected()) {
            log.info("ftp连接池初始化失败");
        }
        return ftp;
    }


    /**
     * 借  获取一个连接对象
     */
    public static FTPClient borrowObject() {
        try {
            return pool.borrowObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 还   归还一个连接对象
     */
    public static void returnObject(FTPClient ftpClient) {
        if (ftpClient != null) {
            pool.returnObject(ftpClient);
        }
    }

    public static boolean upload(String destPath, MultipartFile file) {
        if (!destPath.endsWith(CARVE)) {
            destPath = destPath + CARVE;
        }
        String folder = destPath.substring(0, destPath.lastIndexOf(CARVE) + 1);
        String filename = destPath.substring(destPath.lastIndexOf(CARVE) + 1);
        String originalFilename = file.getOriginalFilename();
        String prefix = originalFilename.substring(0, originalFilename.lastIndexOf(CAS));
        //截取成注意有点 .mp3
        String suffix = originalFilename.substring(originalFilename.lastIndexOf(CAS));

        if (!StringUtils.hasText(filename)) {
            long millis = System.currentTimeMillis();
            filename = millis + RandomUtil.randomNumbers(10) + suffix;
        }
        log.info("正在上传文件：{}，上传到目标目录：{}",originalFilename,folder);
        return upload(folder, destPath + filename, file);
    }


    /**
     * 上传文件
     * @param destPath 目标目录
     * @param file 文件
     * @return 返回上传成功后的ftp文件所在绝对路径
     */
    public static String uploadFile(String destPath, MultipartFile file) {
        log.info("正在上传文件：{}，上传到目标目录：{}",file.getOriginalFilename(),destPath);
        boolean b = upload(destPath,file);
        if (b) {
            if (!destPath.endsWith(CARVE)) {
                destPath = destPath + CARVE;
            }
            String folder = destPath.substring(0, destPath.lastIndexOf(CARVE) + 1);
            String filename = destPath.substring(destPath.lastIndexOf(CARVE) + 1);
            String originalFilename = file.getOriginalFilename();
            String prefix = originalFilename.substring(0, originalFilename.lastIndexOf(CAS));
            //截取成注意有点 .mp3
            String suffix = originalFilename.substring(originalFilename.lastIndexOf(CAS));

            if (!StringUtils.hasText(filename)) {
                long millis = System.currentTimeMillis();
                filename = millis + RandomUtil.randomNumbers(10) + suffix;
            }
            return destPath + filename;
        }
        return null;
    }

    public static boolean upload(String destPath, String filename, MultipartFile file) {
        try {
            FTPClient client = getFtp();
            boolean b = upload(client, destPath, filename, file.getInputStream());
            returnObject(client);
            //关闭流
            file.getInputStream().close();
            return b;
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return false;
    }

    /**
     * 上传文件到指定目录，可选：
     *
     * <pre>
     * 1. destPath为null或""上传到当前路径
     * 2. destPath为相对路径则相对于当前路径的子路径
     * 3. destPath为绝对路径则上传到此路径
     * </pre>
     *
     * @param destPath 服务端路径，可以为{@code null} 或者相对路径或绝对路径
     * @param file     文件
     * @return 是否上传成功
     */
    public static boolean upload(FTPClient client, String destPath, File file) {
        Assert.notNull(file, "file to upload is null !");
        return upload(client, destPath, file.getName(), file);
    }

    /**
     * 上传文件到指定目录，可选：
     *
     * <pre>
     * 1. destPath为null或""上传到当前路径
     * 2. destPath为相对路径则相对于当前路径的子路径
     * 3. destPath为绝对路径则上传到此路径
     * </pre>
     *
     * @param file     文件
     * @param destPath 服务端路径，可以为{@code null} 或者相对路径或绝对路径
     * @param fileName 自定义在服务端保存的文件名
     * @return 是否上传成功
     * @throws IORuntimeException IO异常
     */
    public static boolean upload(FTPClient client, String destPath, String fileName, File file) throws IORuntimeException {
        try (final InputStream in = FileUtil.getInputStream(file)) {
            return upload(client, destPath, fileName, in);
        } catch (final IOException e) {
            throw new IORuntimeException(e);
        }
    }

    /**
     * 上传文件到指定目录，可选：
     *
     * <pre>
     * 1. destPath为null或""上传到当前路径
     * 2. destPath为相对路径则相对于当前路径的子路径
     * 3. destPath为绝对路径则上传到此路径
     * </pre>
     *
     * @param destPath   服务端路径，可以为{@code null} 或者相对路径或绝对路径
     * @param fileName   文件名
     * @param fileStream 文件流
     * @return 是否上传成功
     * @throws IORuntimeException IO异常
     */
    public static boolean upload(FTPClient client, String destPath, String fileName, InputStream fileStream) throws IORuntimeException {
        try {
            client.setFileType(FTPClient.BINARY_FILE_TYPE);
        } catch (final IOException e) {
            throw new IORuntimeException(e);
        }

        String pwd = null;
        if (backToPwd) {
            pwd = pwd(client);
        }

        if (StrUtil.isNotBlank(destPath)) {
            mkDirs(client, destPath);
            if (!cd(client, destPath)) {
                throw new FtpException("Change dir to [{}] error, maybe dir not exist!", destPath);
            }
        }

        try {
            return client.storeFile(fileName, fileStream);
        } catch (IOException e) {
            e.printStackTrace();
            throw new IORuntimeException(e);
        } finally {
            if (backToPwd) {
                cd(client, pwd);
            }
        }
    }

    /**
     * 创建指定文件夹及其父目录，从根目录开始创建，创建完成后回到默认的工作目录
     *
     * @param dir 文件夹路径，绝对路径
     */
    public static void mkDirs(FTPClient client, String dir) {
        final String[] dirs = StrUtil.trim(dir).split("[\\\\/]+");

        final String now = pwd(client);
        if (dirs.length > 0 && StrUtil.isEmpty(dirs[0])) {
            //首位为空，表示以/开头
            cd(client, StrUtil.SLASH);
        }
        for (String s : dirs) {
            if (StrUtil.isNotEmpty(s)) {
                boolean exist = true;
                try {
                    if (!cd(client, s)) {
                        exist = false;
                    }
                } catch (FtpException e) {
                    exist = false;
                }
                if (!exist) {
                    //目录不存在时创建
                    mkdir(client, s);
                    cd(client, s);
                }
            }
        }
        // 切换回工作目录
        cd(client, now);
    }

    /**
     * 在当前远程目录（工作目录）下创建新的目录
     *
     * @param dir 目录名
     * @return 是否创建成功
     */
    public static boolean mkdir(FTPClient client, String dir) {
        try {
            return client.makeDirectory(dir);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }


    public static boolean delFile(String path) {
        FTPClient client = getFtp();
        final String pwd = pwd(client);
        final String fileName = FileUtil.getName(path);
        final String dir = StrUtil.removeSuffix(path, fileName);
        if (!cd(client, dir)) {
            throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path);
        }

        boolean isSuccess;
        try {
            isSuccess = client.deleteFile(fileName);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        } finally {
            // 回到原目录
            cd(client, pwd);
        }
        returnObject(client);
        return isSuccess;
    }

    public static String pwd(FTPClient client) {
        try {
            return client.printWorkingDirectory();
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }


    /**
     * 改变目录
     *
     * @param directory 目录
     * @return 是否成功
     */
    synchronized public static boolean cd(FTPClient client, String directory) {
        if (StrUtil.isBlank(directory)) {
            // 当前目录
            return true;
        }

        try {
            return client.changeWorkingDirectory(directory);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }


    /**
     * 下载文件为字节
     *
     * @param remoteFilePath 文件地址
     * @return 字节
     */
    public static byte[] download(String remoteFilePath) {
        byte[] fileByte = null;
        FTPClient ftpClient = getFtp();
        if (StringUtils.hasText(remoteFilePath)) {
            try {
                ftpClient.changeWorkingDirectory(remoteFilePath);
            } catch (Exception e) {
                log.info("e:", e.getMessage());
                //ftpClient.completePendingCommand();
                returnObject(ftpClient);
                return null;
            }
            log.info("file success");
            try (InputStream in = ftpClient.retrieveFileStream(encodingPath(remoteFilePath));
                 ByteArrayOutputStream out = new ByteArrayOutputStream()) {
                if (in == null) {
                    throw new RuntimeException("读取文件不存在");
                }
                int size;
                byte[] buf = new byte[10240];
                while ((size = in.read(buf)) > 0) {
                    out.write(buf, 0, size);
                    out.flush();
                }
                fileByte = out.toByteArray();
                out.close();
                in.close();
                ftpClient.completePendingCommand();
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                returnObject(ftpClient);
            }
        }
        return fileByte;
    }

    /**
     * 下载文件为字节
     *
     * @param remoteFilePath 文件地址
     * @return 字节
     */
    public static void downloadFile(String remoteFilePath) {
        byte[] bytes = download(remoteFilePath);
        String fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf(CARVE) + 1);
        export(fileName,bytes, HttpServletUtils.getServletResponse());
    }

    /**
     * 导出文件流
     *
     * @param fileName 文件名
     * @param bytes 文件铺
     * @param response 输出流
     */
    private static void export(String fileName, byte[] bytes, HttpServletResponse response) {
        try (OutputStream ouputStream = response.getOutputStream()) {
            // 设置输出的格式
            String name = URLEncoder.encode(fileName, "UTF-8");
            //自定义的header
            response.setHeader("requestType", "file");
            // 自定义的header
            response.setHeader("requestFileName", name);
            //设置这个header 可见
            response.setHeader("Access-Control-Expose-Headers", "requestType,requestFileName");

            //设置响应类型为html，编码为utf-8，处理相应页面文本显示的乱码
            response.setContentType("application/octet-stream;charset=UTF-8");
            //设置文件头：最后一个参数是设置下载文件名
            response.setHeader("Content-disposition", "attachment;fileName=" + name);
            ouputStream.write(bytes);
            ouputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 编码文件路径
     */
    private static String encodingPath(String path) throws UnsupportedEncodingException {
        // FTP协议里面，规定文件名编码为iso-8859-1，所以目录名或文件名需要转码
        return new String(path.replaceAll("//", "/").getBytes("utf-8"), "iso-8859-1");
    }
}
